สำรวจไปป์ไลน์ WebGL Mesh Shader ที่ปฏิวัติวงการ เรียนรู้วิธีที่ Task Amplification ช่วยให้สร้างเรขาคณิตจำนวนมหาศาลได้ทันที และการคัดกรองขั้นสูงสำหรับกราฟิกเว็บยุคใหม่
ปลดปล่อยพลังเรขาคณิต: เจาะลึกไปกับไปป์ไลน์ Task Amplification ของ Mesh Shader ใน WebGL
เว็บไม่ได้เป็นเพียงสื่อสองมิติที่หยุดนิ่งอีกต่อไป มันได้พัฒนาไปเป็นแพลตฟอร์มที่มีชีวิตชีวาสำหรับประสบการณ์ 3D ที่สมบูรณ์และดื่มด่ำ ตั้งแต่ตัวกำหนดค่าผลิตภัณฑ์ที่น่าทึ่งและการแสดงภาพทางสถาปัตยกรรม ไปจนถึงแบบจำลองข้อมูลที่ซับซ้อนและเกมที่สมบูรณ์ การวิวัฒนาการนี้อย่างไรก็ตาม ทำให้เกิดความต้องการที่ไม่เคยมีมาก่อนในหน่วยประมวลผลกราฟิก (GPU) เป็นเวลาหลายปีที่ไปป์ไลน์กราฟิกแบบเรียลไทม์มาตรฐาน แม้ว่าจะมีประสิทธิภาพ แต่ก็แสดงให้เห็นถึงอายุของมัน โดยมักจะทำหน้าที่เป็นคอขวดสำหรับความซับซ้อนทางเรขาคณิตที่แอปพลิเคชันสมัยใหม่ต้องการ
เข้าสู่ไปป์ไลน์ Mesh Shader ซึ่งเป็นคุณสมบัติที่เปลี่ยนแปลงกระบวนทัศน์ที่สามารถเข้าถึงได้บนเว็บผ่านส่วนขยาย WEBGL_mesh_shader โมเดลใหม่นี้เปลี่ยนวิธีการที่เราคิดและประมวลผลเรขาคณิตบน GPU อย่างสิ้นเชิง หัวใจสำคัญคือแนวคิดที่ทรงพลัง: Task Amplification นี่ไม่ใช่แค่การอัปเดตเพิ่มเติม แต่เป็นการก้าวกระโดดครั้งใหญ่ที่ย้ายตรรกะการจัดกำหนดการและการสร้างเรขาคณิตจาก CPU ไปยังสถาปัตยกรรมคู่ขนานสูงของ GPU โดยตรง ปลดล็อกความเป็นไปได้ที่ไม่สามารถทำได้จริงหรือเป็นไปไม่ได้ในเว็บเบราว์เซอร์
คู่มือที่ครอบคลุมนี้จะนำคุณไปเจาะลึกไปป์ไลน์เรขาคณิต mesh shader เราจะสำรวจสถาปัตยกรรม ทำความเข้าใจบทบาทที่แตกต่างกันของ Task และ Mesh shader และเปิดเผยว่า task amplification สามารถนำมาใช้เพื่อสร้างแอปพลิเคชันเว็บที่สวยงามและมีประสิทธิภาพได้อย่างไร
ย้อนอดีตอย่างรวดเร็ว: ข้อจำกัดของไปป์ไลน์เรขาคณิตแบบดั้งเดิม
เพื่อให้เข้าใจถึงนวัตกรรมของ mesh shader อย่างแท้จริง เราต้องเข้าใจไปป์ไลน์ที่พวกมันเข้ามาแทนที่ก่อนหน้านี้ เป็นเวลาหลายทศวรรษที่กราฟิกแบบเรียลไทม์ถูกครอบงำโดยไปป์ไลน์ฟังก์ชันคงที่ค่อนข้างมาก:
- Vertex Shader: ประมวลผล vertices แต่ละรายการ แปลงเป็นพื้นที่หน้าจอ
- (Optional) Tessellation Shaders: แบ่งส่วนของเรขาคณิตเพื่อสร้างรายละเอียดที่ละเอียดยิ่งขึ้น
- (Optional) Geometry Shader: สามารถสร้างหรือทำลาย primitives (จุด เส้น สามเหลี่ยม) ได้ทันที
- Rasterizer: แปลง primitives เป็นพิกเซล
- Fragment Shader: คำนวณสีสุดท้ายของแต่ละพิกเซล
โมเดลนี้ให้บริการเราได้ดี แต่มันมีข้อจำกัดโดยธรรมชาติ โดยเฉพาะอย่างยิ่งเมื่อฉากมีความซับซ้อนมากขึ้น:
- CPU-Bound Draw Calls: CPU มีงานมากมายในการคิดว่าอะไรที่ต้องวาดอย่างแม่นยำ ซึ่งเกี่ยวข้องกับการ frustum culling (การลบวัตถุที่อยู่นอกมุมมองของกล้อง) occlusion culling (การลบวัตถุที่ซ่อนอยู่โดยวัตถุอื่น) และการจัดการระบบ level-of-detail (LOD) สำหรับฉากที่มีวัตถุหลายล้านชิ้น สิ่งนี้สามารถนำไปสู่การที่ CPU กลายเป็นคอขวดหลัก ไม่สามารถป้อน GPU ที่หิวโหยได้เร็วพอ
- Rigid Input Structure: ไปป์ไลน์ถูกสร้างขึ้นจากโมเดลการประมวลผลอินพุตที่เข้มงวด Input Assembler ป้อน vertices ทีละรายการ และ shader ประมวลผลในลักษณะที่ค่อนข้างจำกัด สิ่งนี้ไม่เหมาะสำหรับสถาปัตยกรรม GPU สมัยใหม่ ซึ่งมีความโดดเด่นในการประมวลผลข้อมูลแบบขนานที่สอดคล้องกัน
- Inefficient Amplification: ในขณะที่ Geometry Shaders อนุญาตให้มีการ amplification เรขาคณิต (การสร้างสามเหลี่ยมใหม่จาก primitive อินพุต) แต่ก็มีประสิทธิภาพที่ต่ำอย่างมาก พฤติกรรมการเอาต์พุตของพวกเขามักจะคาดเดาไม่ได้สำหรับฮาร์ดแวร์ ซึ่งนำไปสู่ปัญหาด้านประสิทธิภาพที่ทำให้พวกเขาเป็นจุดเริ่มต้นที่ไม่ดีสำหรับแอปพลิเคชันขนาดใหญ่จำนวนมาก
- Wasted Work: ในไปป์ไลน์แบบดั้งเดิม หากคุณส่งสามเหลี่ยมที่จะเรนเดอร์ vertex shader จะทำงานสามครั้ง แม้ว่าสามเหลี่ยมนั้นจะถูกคัดกรองในที่สุดหรือเป็นแผ่นบาง ๆ ที่หันหลังกลับ พลังการประมวลผลจำนวนมากถูกใช้ไปกับเรขาคณิตที่ไม่ได้มีส่วนช่วยอะไรในภาพสุดท้าย
การเปลี่ยนแปลงกระบวนทัศน์: แนะนำไปป์ไลน์ Mesh Shader
ไปป์ไลน์ Mesh Shader แทนที่ขั้นตอน Vertex, Tessellation และ Geometry shader ด้วยโมเดลสองขั้นตอนใหม่ที่ยืดหยุ่นกว่า:
- Task Shader (Optional): ขั้นตอนการควบคุมระดับสูงที่กำหนดปริมาณงานที่ต้องทำ หรือที่เรียกว่า Amplification Shader
- Mesh Shader: ขั้นตอนการทำงานที่ทำงานกับชุดข้อมูลเพื่อสร้างแพ็กเก็ตเรขาคณิตขนาดเล็กที่มีอยู่ในตัวเองที่เรียกว่า "meshlets"
แนวทางใหม่นี้เปลี่ยนปรัชญาการเรนเดอร์อย่างสิ้นเชิง แทนที่จะให้ CPU จัดการทุก draw call สำหรับทุกวัตถุอย่างละเอียด ตอนนี้ CPU สามารถออกคำสั่ง draw ที่ทรงพลังเพียงครั้งเดียว ซึ่งโดยพื้นฐานแล้วจะบอก GPU ว่า: "นี่คือคำอธิบายระดับสูงของฉากที่ซับซ้อน คุณคิดรายละเอียดเอง"
GPU โดยใช้ Task และ Mesh shader จากนั้นสามารถทำการคัดกรอง การเลือก LOD และการสร้างขั้นตอนในลักษณะคู่ขนานสูง เปิดใช้งานเฉพาะงานที่จำเป็นในการสร้างเรขาคณิตที่จะมองเห็นได้จริง นี่คือสาระสำคัญของ GPU-driven rendering pipeline และเป็นตัวเปลี่ยนเกมสำหรับประสิทธิภาพและความสามารถในการปรับขนาด
The Conductor: ทำความเข้าใจ Task (Amplification) Shader
Task Shader คือสมองของไปป์ไลน์ใหม่และเป็นกุญแจสำคัญสำหรับพลังอันเหลือเชื่อ มันเป็นขั้นตอนเสริม แต่เป็นที่ที่เกิด "amplification" บทบาทหลักของมันไม่ใช่การสร้าง vertices หรือ triangles แต่ทำหน้าที่เป็นผู้จัดส่งงาน
Task Shader คืออะไร?
คิดว่า Task Shader เป็นผู้จัดการโครงการสำหรับโครงการก่อสร้างขนาดใหญ่ CPU ให้เป้าหมายระดับสูงแก่ผู้จัดการ เช่น "สร้างย่านในเมือง" ผู้จัดการโครงการ (Task Shader) ไม่ได้วางอิฐด้วยตัวเอง แต่จะประเมินงานโดยรวม ตรวจสอบพิมพ์เขียว และกำหนดว่าทีมงานก่อสร้างใด (Mesh Shader workgroups) ที่จำเป็นและจำนวนเท่าใด สามารถตัดสินใจได้ว่าอาคารบางหลังไม่จำเป็น (culling) หรือพื้นที่เฉพาะต้องการทีมงานสิบทีมในขณะที่อีกแห่งต้องการเพียงสองทีม
ในแง่ทางเทคนิค Task Shader ทำงานเป็น workgroup คล้ายกับการคำนวณ สามารถเข้าถึงหน่วยความจำ ทำการคำนวณที่ซับซ้อน และที่สำคัญที่สุดคือ ตัดสินใจว่าจะเปิดตัว Mesh Shader workgroups จำนวนเท่าใด การตัดสินใจนี้เป็นหัวใจสำคัญของพลังของมัน
พลังแห่ง Amplification
คำว่า "amplification" มาจากความสามารถของ Task Shader ในการใช้ workgroup ของตัวเองเพียงกลุ่มเดียวและเปิดตัว Mesh Shader workgroups ศูนย์ หนึ่ง หรือหลายกลุ่ม ความสามารถนี้เป็นการเปลี่ยนแปลง:
- Launch Zero: หาก Task Shader กำหนดว่าวัตถุหรือส่วนหนึ่งของฉากไม่สามารถมองเห็นได้ (เช่น อยู่นอก frustum ของกล้อง) ก็สามารถเลือกที่จะเปิดตัว Mesh Shader workgroups ศูนย์ ได้ งานทั้งหมดที่เกี่ยวข้องกับวัตถุนั้นจะหายไปโดยไม่ได้รับการประมวลผลเพิ่มเติม นี่คือการคัดกรองที่มีประสิทธิภาพอย่างเหลือเชื่อซึ่งดำเนินการทั้งหมดบน GPU
- Launch One: นี่คือการส่งผ่านโดยตรง Task Shader workgroup ตัดสินใจว่าจำเป็นต้องมี Mesh Shader workgroup หนึ่งกลุ่ม
- Launch Many: นี่คือที่ที่เวทมนตร์เกิดขึ้นสำหรับการสร้างขั้นตอน Task Shader workgroup เดียวสามารถวิเคราะห์พารามิเตอร์อินพุตบางอย่างและตัดสินใจที่จะเปิดตัว Mesh Shader workgroups หลายพันกลุ่ม ตัวอย่างเช่น สามารถเปิดตัว workgroup สำหรับหญ้าทุกใบในทุ่งหรือดาวเคราะห์น้อยทุกดวงในกลุ่มดาวที่หนาแน่น ทั้งหมดนี้มาจากคำสั่ง dispatch เดียวจาก CPU
มุมมองเชิงแนวคิดของ Task Shader GLSL
ในขณะที่รายละเอียดอาจซับซ้อน กลไก amplification หลักใน GLSL (สำหรับส่วนขยาย WebGL) นั้นง่ายอย่างน่าประหลาดใจ มันเกี่ยวข้องกับฟังก์ชัน `EmitMeshTasksEXT()`
หมายเหตุ: นี่คือตัวอย่างเชิงแนวคิดที่เรียบง่าย
#version 310 es
#extension GL_EXT_mesh_shader : require
layout(local_size_x = 32, local_size_y = 1, local_size_z = 1) in;
// Uniforms ที่ส่งมาจาก CPU
uniform mat4 u_viewProjectionMatrix;
uniform uint u_totalObjectCount;
// บัฟเฟอร์ที่มี bounding spheres สำหรับวัตถุจำนวนมาก
struct BoundingSphere {
vec4 centerAndRadius;
};
layout(std430, binding = 0) readonly buffer ObjectBounds {
BoundingSphere bounds[];
} objectBounds;
void main() {
// แต่ละเธรดใน workgroup สามารถตรวจสอบวัตถุที่แตกต่างกันได้
uint objectIndex = gl_GlobalInvocationID.x;
if (objectIndex >= u_totalObjectCount) {
return;
}
// ทำการ frustum culling บน GPU สำหรับ bounding sphere ของวัตถุนี้
BoundingSphere sphere = objectBounds.bounds[objectIndex];
bool isVisible = isSphereInFrustum(sphere.centerAndRadius, u_viewProjectionMatrix);
// หากมองเห็นได้ ให้เปิดตัว Mesh Shader workgroup หนึ่งกลุ่มเพื่อวาด
// หมายเหตุ: ตรรกะนี้อาจซับซ้อนกว่า โดยใช้ atomicsเพื่อนับที่มองเห็นได้
// วัตถุและให้หนึ่งเธรด dispatch สำหรับวัตถุทั้งหมด
if (isVisible) {
// สิ่งนี้จะบอก GPU ให้เปิดใช้งาน mesh task พารามิเตอร์สามารถใช้ได้
// เพื่อส่งข้อมูลไปยัง Mesh Shader workgroup
// เพื่อความเรียบง่าย เราจินตนาการว่าการเรียกใช้ task shader แต่ละครั้งสามารถแมปโดยตรงกับ mesh task ได้
// สถานการณ์ที่สมจริงกว่าเกี่ยวข้องกับการจัดกลุ่มและการ dispatch จากเธรดเดียว
// A simplified conceptual dispatch:
// We'll pretend each visible object gets its own task, though in reality
// one task shader invocation would manage dispatching multiple mesh shaders.
EmitMeshTasksEXT(1u, 0u, 0u); // นี่คือฟังก์ชัน amplification หลัก
}
// หากมองไม่เห็น เราจะไม่ทำอะไรเลย! วัตถุถูกคัดกรองโดยไม่มีค่าใช้จ่าย GPU เกินกว่าการตรวจสอบนี้
}
ในสถานการณ์จริง คุณอาจมีหนึ่งเธรดใน workgroup รวบรวมผลลัพธ์และทำการเรียก `EmitMeshTasksEXT` เพียงครั้งเดียวสำหรับวัตถุที่มองเห็นได้ทั้งหมดที่ workgroup รับผิดชอบ
The Workforce: บทบาทของ Mesh Shader ในการสร้างเรขาคณิต
เมื่อ Task Shader ได้ dispatch workgroups หนึ่งกลุ่มขึ้นไป Mesh Shader จะเข้ามาแทนที่ หาก Task Shader เป็นผู้จัดการโครงการ Mesh Shader คือทีมงานก่อสร้างที่มีทักษะที่สร้างเรขาคณิตจริง
จาก Workgroups สู่ Meshlets
เช่นเดียวกับ Task Shader Mesh Shader ทำงานเป็น workgroup ที่ทำงานร่วมกันของเธรด เป้าหมายโดยรวมของ workgroup ทั้งหมดนี้คือการสร้างเรขาคณิตขนาดเล็กเพียงชุดเดียวที่เรียกว่า meshlet meshlet เป็นเพียงคอลเล็กชันของ vertices และ primitives (สามเหลี่ยม) ที่เชื่อมต่อพวกมัน โดยทั่วไป meshlet มี vertices จำนวนเล็กน้อย (เช่น สูงสุด 128) และ triangles (เช่น สูงสุด 256) ซึ่งเป็นขนาดที่เป็นมิตรกับแคช GPU และโมเดลการประมวลผลสมัยใหม่
นี่คือการออกเดินทางขั้นพื้นฐานจาก vertex shader ซึ่งไม่มีแนวคิดเกี่ยวกับเพื่อนบ้าน ใน Mesh Shader เธรดทั้งหมดใน workgroup สามารถแชร์หน่วยความจำและประสานความพยายามในการสร้าง meshlet อย่างมีประสิทธิภาพ
การสร้าง Vertices และ Primitives
แทนที่จะส่งคืน `gl_Position` เพียงตัวเดียว Mesh Shader workgroup จะเติมข้อมูลอาร์เรย์เอาต์พุตด้วยข้อมูลที่สมบูรณ์สำหรับ meshlet เธรดทำงานร่วมกันเพื่อเขียนตำแหน่ง vertex, normals, พิกัด UV และแอตทริบิวต์อื่น ๆ ลงในอาร์เรย์เหล่านี้ พวกเขายังกำหนด primitives โดยระบุว่า vertices ใดที่สร้างแต่ละสามเหลี่ยม
ขั้นตอนสุดท้ายใน Mesh Shader คือการเรียกใช้ฟังก์ชันเช่น `SetMeshOutputsEXT()` เพื่อประกาศจำนวน vertices และ primitives ที่สร้างขึ้นอย่างแม่นยำ จากนั้นฮาร์ดแวร์จะใช้ meshlet นี้และส่งไปยัง rasterizer โดยตรง
มุมมองเชิงแนวคิดของ Mesh Shader GLSL
นี่คือตัวอย่างเชิงแนวคิดของ Mesh Shader ที่สร้าง quad อย่างง่าย โปรดทราบว่าเธรดทำงานร่วมกันอย่างไรโดยอิงตาม `gl_LocalInvocationID`
#version 310 es
#extension GL_EXT_mesh_shader : require
// กำหนดเอาต์พุตสูงสุดสำหรับ meshlet ของเรา
layout(max_vertices = 4, max_primitives = 2) out;
layout(triangles) out;
layout(local_size_x = 4, local_size_y = 1, local_size_z = 1) in;
// เราเขียนข้อมูล vertex ไปยังอาร์เรย์เอาต์พุตในตัวเหล่านี้
out gl_MeshVerticesEXT {
vec4 position;
vec2 uv;
} vertices[];
// เราเขียนดัชนีสามเหลี่ยมไปยังอาร์เรย์นี้
out uint gl_MeshPrimitivesEXT[];
uniform mat4 u_modelViewProjectionMatrix;
void main() {
// จำนวน vertices และ primitives ทั้งหมดที่จะสร้างสำหรับ meshlet นี้
const uint vertexCount = 4;
const uint primitiveCount = 2;
// บอกฮาร์ดแวร์ว่าเรากำลังส่งออก vertices และ primitives จำนวนเท่าใดจริง ๆ
SetMeshOutputsEXT(vertexCount, primitiveCount);
// กำหนดตำแหน่ง vertex และ UV สำหรับ quad
vec4 positions[4] = vec4[4](
vec4(-0.5, 0.5, 0.0, 1.0),
vec4(-0.5, -0.5, 0.0, 1.0),
vec4(0.5, 0.5, 0.0, 1.0),
vec4(0.5, -0.5, 0.0, 1.0)
);
vec2 uvs[4] = vec2[4](
vec2(0.0, 1.0),
vec2(0.0, 0.0),
vec2(1.0, 1.0),
vec2(1.0, 0.0)
);
// ให้แต่ละเธรดใน workgroup สร้างหนึ่ง vertex
uint id = gl_LocalInvocationID.x;
if (id < vertexCount) {
vertices[id].position = u_modelViewProjectionMatrix * positions[id];
vertices[id].uv = uvs[id];
}
// ให้สองเธรดแรกสร้างสองสามเหลี่ยมสำหรับ quad
if (id == 0) {
// สามเหลี่ยมแรก: 0, 1, 2
gl_MeshPrimitivesEXT[0] = 0u;
gl_MeshPrimitivesEXT[1] = 1u;
gl_MeshPrimitivesEXT[2] = 2u;
}
if (id == 1) {
// สามเหลี่ยมที่สอง: 1, 3, 2
gl_MeshPrimitivesEXT[3] = 1u;
gl_MeshPrimitivesEXT[4] = 3u;
gl_MeshPrimitivesEXT[5] = 2u;
}
}
Practical Magic: กรณีการใช้งานสำหรับ Task Amplification
พลังที่แท้จริงของไปป์ไลน์นี้จะถูกเปิดเผยเมื่อเรานำไปใช้กับความท้าทายในการเรนเดอร์ที่ซับซ้อนในโลกแห่งความเป็นจริง
กรณีการใช้งานที่ 1: การสร้างเรขาคณิตขั้นตอนขนาดใหญ่
ลองจินตนาการถึงการเรนเดอร์สนามดาวเคราะห์น้อยที่หนาแน่นซึ่งมีดาวเคราะห์น้อยที่ไม่ซ้ำกันหลายแสนดวง ด้วยไปป์ไลน์เก่า CPU จะต้องสร้างข้อมูล vertex ของดาวเคราะห์น้อยแต่ละดวงและออก draw call แยกต่างหากสำหรับแต่ละดวง ซึ่งเป็นแนวทางที่ไม่สามารถทำได้
เวิร์กโฟลว์ Mesh Shader:
- CPU ออก หนึ่ง draw call: `drawMeshTasksEXT(1, 1)` นอกจากนี้ยังส่งพารามิเตอร์ระดับสูงบางอย่าง เช่น รัศมีของสนามและความหนาแน่นของดาวเคราะห์น้อย ในบัฟเฟอร์ uniform
- Task Shader workgroup เดียวดำเนินการ อ่านพารามิเตอร์และคำนวณว่าจำเป็นต้องมีดาวเคราะห์น้อย 50,000 ดวง จากนั้นจึงเรียก `EmitMeshTasksEXT(50000, 0, 0)`
- GPU เปิดตัว Mesh Shader workgroups 50,000 กลุ่มแบบขนาน
- Mesh Shader workgroup แต่ละกลุ่มใช้ ID ที่ไม่ซ้ำกัน (`gl_WorkGroupID`) เป็น seed เพื่อสร้าง vertices และ triangles สำหรับดาวเคราะห์น้อยที่ไม่ซ้ำกันหนึ่งดวงโดยขั้นตอน
ผลลัพธ์คือฉากขนาดใหญ่ที่ซับซ้อนซึ่งสร้างขึ้นเกือบทั้งหมดบน GPU ปล่อยให้ CPU จัดการงานอื่น ๆ เช่น ฟิสิกส์และ AI
กรณีการใช้งานที่ 2: GPU-Driven Culling ในระดับที่ยิ่งใหญ่
พิจารณาฉากเมืองที่มีรายละเอียดพร้อมวัตถุแต่ละรายการหลายล้านชิ้น CPU ไม่สามารถตรวจสอบการมองเห็นของทุกวัตถุในแต่ละเฟรมได้
เวิร์กโฟลว์ Mesh Shader:
- CPU อัปโหลดบัฟเฟอร์ขนาดใหญ่ที่มี bounding volumes (เช่น spheres หรือ boxes) สำหรับทุกวัตถุในฉาก สิ่งนี้เกิดขึ้นหนึ่งครั้ง หรือเมื่อวัตถุเคลื่อนที่เท่านั้น
- CPU ออก draw call เดียว เปิดตัว Task Shader workgroups ให้เพียงพอเพื่อประมวลผลรายการ bounding volumes ทั้งหมดแบบขนาน
- Task Shader workgroup แต่ละกลุ่มจะได้รับส่วนหนึ่งของรายการ bounding volume วนซ้ำวัตถุที่กำหนด ทำการ frustum culling (และอาจ occlusion culling) สำหรับแต่ละวัตถุ และนับจำนวนที่มองเห็นได้
- สุดท้าย เปิดตัว Mesh Shader workgroups จำนวนที่แน่นอน โดยส่ง ID ของวัตถุที่มองเห็นได้
- Mesh Shader workgroup แต่ละกลุ่มจะได้รับ ID วัตถุ ค้นหาข้อมูล mesh จากบัฟเฟอร์ และสร้าง meshlets ที่สอดคล้องกันสำหรับการเรนเดอร์
สิ่งนี้ย้ายกระบวนการคัดกรองทั้งหมดไปยัง GPU ทำให้สามารถสร้างฉากที่มีความซับซ้อนซึ่งจะทำให้แนวทางที่ใช้ CPU เป็นหลักเป็นอัมพาตทันที
กรณีการใช้งานที่ 3: Level of Detail (LOD) แบบไดนามิกและมีประสิทธิภาพ
ระบบ LOD มีความสำคัญต่อประสิทธิภาพ โดยเปลี่ยนไปใช้โมเดลที่ง่ายกว่าสำหรับวัตถุที่อยู่ไกลออกไป mesh shaders ทำให้กระบวนการนี้ละเอียดและมีประสิทธิภาพมากขึ้น
เวิร์กโฟลว์ Mesh Shader:
- ข้อมูลของวัตถุถูกประมวลผลล่วงหน้าเป็นลำดับชั้นของ meshlets LOD ที่หยาบกว่าใช้ meshlets ที่มีขนาดใหญ่กว่าจำนวนน้อยกว่า
- Task Shader สำหรับวัตถุนี้คำนวณระยะห่างจากกล้อง
- ตามระยะทาง จะตัดสินใจว่าระดับ LOD ใดเหมาะสม จากนั้นสามารถทำการคัดกรองบนพื้นฐาน per-meshlet สำหรับ LOD นั้น ตัวอย่างเช่น สำหรับวัตถุขนาดใหญ่ สามารถคัดกรอง meshlets ที่ด้านหลังของวัตถุที่ไม่สามารถมองเห็นได้
- เปิดใช้งานเฉพาะ Mesh Shader workgroups สำหรับ meshlets ที่มองเห็นได้ของ LOD ที่เลือก
สิ่งนี้ช่วยให้สามารถเลือกระดับ LOD ที่ละเอียดและทันที และการคัดกรองที่มีประสิทธิภาพมากกว่าการที่ CPU สลับโมเดลทั้งหมด
เริ่มต้นใช้งาน: การใช้ส่วนขยาย `WEBGL_mesh_shader`
พร้อมที่จะทดลองแล้วหรือยัง นี่คือขั้นตอนการปฏิบัติเพื่อเริ่มต้นใช้งาน mesh shaders ใน WebGL
การตรวจสอบการสนับสนุน
ก่อนอื่น นี่เป็นคุณสมบัติที่ทันสมัย คุณต้องตรวจสอบว่าเบราว์เซอร์และฮาร์ดแวร์ของผู้ใช้รองรับ
const gl = canvas.getContext('webgl2');
const meshShaderExtension = gl.getExtension('WEBGL_mesh_shader');
if (!meshShaderExtension) {
console.error("Your browser or GPU does not support WEBGL_mesh_shader.");
// Fallback to a traditional rendering path
}
Draw Call ใหม่
ลืม `drawArrays` และ `drawElements` ไปได้เลย ไปป์ไลน์ใหม่ถูกเรียกใช้ด้วยคำสั่งใหม่ วัตถุส่วนขยายที่คุณได้รับจาก `getExtension` จะมีฟังก์ชันใหม่
// เปิดตัว Task Shader workgroups 10 กลุ่ม
// แต่ละ workgroup จะมี local_size ที่กำหนดไว้ใน shader
meshShaderExtension.drawMeshTasksEXT(0, 10);
อาร์กิวเมนต์ `count` ระบุจำนวน local workgroups ของ Task Shader ที่จะเปิดใช้งาน หากคุณไม่ได้ใช้ Task Shader สิ่งนี้จะเปิดใช้งาน Mesh Shader workgroups โดยตรง
การคอมไพล์และการเชื่อมโยง Shader
กระบวนการนี้คล้ายกับ GLSL แบบดั้งเดิม แต่คุณจะสร้าง shaders ประเภท `meshShaderExtension.MESH_SHADER_EXT` และ `meshShaderExtension.TASK_SHADER_EXT` คุณเชื่อมโยงเข้าด้วยกันในโปรแกรมเช่นเดียวกับที่คุณทำ vertex และ fragment shader
ที่สำคัญคือ ซอร์สโค้ด GLSL ของคุณสำหรับ shaders ทั้งสองจะต้องเริ่มต้นด้วยคำสั่งเพื่อให้เปิดใช้งานส่วนขยาย:
#extension GL_EXT_mesh_shader : require
ข้อควรพิจารณาด้านประสิทธิภาพและแนวทางปฏิบัติที่ดีที่สุด
- เลือกขนาด Workgroup ที่เหมาะสม: `layout(local_size_x = N)` ใน shader ของคุณมีความสำคัญ ขนาด 32 หรือ 64 มักจะเป็นจุดเริ่มต้นที่ดี เนื่องจากสอดคล้องกับสถาปัตยกรรมฮาร์ดแวร์พื้นฐานได้ดี แต่ควรสร้างโปรไฟล์เสมอเพื่อค้นหาขนาดที่เหมาะสมที่สุดสำหรับปริมาณงานเฉพาะของคุณ
- ทำให้ Task Shader ของคุณ Lean: Task Shader เป็นเครื่องมือที่ทรงพลัง แต่ก็เป็นคอขวดที่อาจเกิดขึ้น การคัดกรองและตรรกะที่คุณทำที่นี่ควรมีประสิทธิภาพมากที่สุด หลีกเลี่ยงการคำนวณที่ช้าและซับซ้อนหากสามารถคำนวณล่วงหน้าได้
- ปรับขนาด Meshlet ให้เหมาะสม: มีจุดที่เหมาะสมที่ขึ้นอยู่กับฮาร์ดแวร์สำหรับจำนวน vertices และ primitives ต่อ meshlet `max_vertices` และ `max_primitives` ที่คุณประกาศควรได้รับการเลือกอย่างระมัดระวัง หากเล็กเกินไป ค่าใช้จ่ายในการเปิดตัว workgroups จะโดดเด่น หากใหญ่เกินไป คุณจะสูญเสียความขนานและประสิทธิภาพของแคช
- ความสอดคล้องของข้อมูลมีความสำคัญ: เมื่อทำการคัดกรองใน Task Shader ให้จัดเรียงข้อมูล bounding volume ในหน่วยความจำเพื่อส่งเสริมรูปแบบการเข้าถึงที่สอดคล้องกัน สิ่งนี้ช่วยให้แคช GPU ทำงานได้อย่างมีประสิทธิภาพ
- รู้ว่าเมื่อใดควรหลีกเลี่ยง: Mesh shaders ไม่ใช่กระสุนวิเศษ สำหรับการเรนเดอร์วัตถุอย่างง่ายจำนวนน้อย ค่าใช้จ่ายของ mesh pipeline อาจช้ากว่า vertex pipeline แบบดั้งเดิม ใช้ในที่ที่จุดแข็งของพวกเขาเปล่งประกาย: จำนวนวัตถุจำนวนมาก การสร้างขั้นตอนที่ซับซ้อน และปริมาณงานที่ขับเคลื่อนด้วย GPU
สรุป: อนาคตของกราฟิกแบบเรียลไทม์บนเว็บอยู่ในขณะนี้
ไปป์ไลน์ Mesh Shader พร้อม Task Amplification แสดงถึงหนึ่งในการพัฒนาที่สำคัญที่สุดในกราฟิกแบบเรียลไทม์ในช่วงทศวรรษที่ผ่านมา ด้วยการเปลี่ยนกระบวนทัศน์จากกระบวนการที่ CPU จัดการอย่างเข้มงวดไปสู่กระบวนการที่ GPU ขับเคลื่อนอย่างยืดหยุ่น มันทำลายอุปสรรคก่อนหน้านี้ต่อความซับซ้อนทางเรขาคณิตและขนาดฉาก
เทคโนโลยีนี้ สอดคล้องกับทิศทางของ API กราฟิกสมัยใหม่ เช่น Vulkan, DirectX 12 Ultimate และ Metal ไม่ได้จำกัดอยู่เพียงแอปพลิเคชันเนทีฟระดับไฮเอนด์อีกต่อไป การมาถึงของมันใน WebGL เปิดประตูสำหรับยุคใหม่ของประสบการณ์บนเว็บที่มีรายละเอียด ไดนามิก และดื่มด่ำกว่าที่เคย สำหรับนักพัฒนาที่เต็มใจที่จะยอมรับโมเดลใหม่นี้ ความเป็นไปได้ที่สร้างสรรค์นั้นแทบจะไร้ขีดจำกัด พลังในการสร้างโลกทั้งใบได้ทันทีนั้น เป็นครั้งแรกที่ปลายนิ้วของคุณอย่างแท้จริง ภายในเว็บเบราว์เซอร์